import EmailExistException from "../../exception/emailExist.ex";
import { Router, Handler } from "express";
import Controller from "interface/controller.if";
import validationMiddleware from "../../middleware/validation.mw";
import CreateUserDto from "./user.create.dto";
import userModel from "./user.model";
import bcrypt from "bcrypt";
import HttpException from "../../exception/http.ex";
import LogginDto from "./user.login.dto";
import WrongCredentialException from "../../exception/wrongCredential.ex";
import LoginDto from "./user.login.dto";
import TokenData from "interface/tokenData.if";
import DataStoredInToken from "interface/DataStoredInToken.if";
import jwt from "jsonwebtoken";

/**
 * 验证类 功能的控制器
 */
class AuthenticationController implements Controller {
  router: Router;

  // 有必要 对外暴露path吗？ 即使是getter?暂时看起来没必要
  private routePath: string = "/auth";
  private user = userModel;

  constructor() {
    this.router = Router();
    this.useRoutes();
  }
  private useRoutes() {
    this.router.post(
      `${this.routePath}/register`,
      validationMiddleware(CreateUserDto),
      this.registration
    );
    this.router.post(
      `${this.routePath}/login`,
      validationMiddleware(LoginDto),
      this.login
    );
  }

  /**
   *
   * @param userId 登录成功后数据库返回的信息中的 _id
   * 只使用_id 签发token
   * @returns
   */
  private static createToken(userId: string): TokenData {
    const expiresIn = 60 * 60; // an hour
    const secret = process.env.JWT_SECRET;
    const dataStoredInToken: DataStoredInToken = {
      _id: userId,
    };
    return {
      expiresIn,
      token: jwt.sign(dataStoredInToken, secret, { expiresIn }),
    };
  }

  /**
   *
   * @param tokenData  使用签发完毕的token 数据 @see createToken
   * @returns 返回一个cookie 配置
   */
  private static createCookie(tokenData: TokenData) {
    return `Authorization=${tokenData.token}; HttpOnly; Max-Age=${tokenData.expiresIn}`;
  }

  /**
   *
   * @param userId  数据库中某user的_id
   * @returns cookies配置
   */
  private static signTokenAndGetCookie = (userId: string) => {
    const tokenData = AuthenticationController.createToken(userId);
    const cookies = AuthenticationController.createCookie(tokenData);
    return cookies;
  };

  // 而不是  private someFn(req:Request,res:Response,next:NextFunction)...
  private registration: Handler = async (req, res, next) => {
    console.log("some");

    try {
      const userData: CreateUserDto = req.body;
      const email = await this.user.findOne({ email: userData.email });

      if (email) {
        next(new EmailExistException(userData.email));
        return;
      } else {
        const hashedPassword = await bcrypt.hash(userData.password, 10);
        const user = await this.user.create({
          ...userData,
          password: hashedPassword,
        });
        user.password = undefined;

        res.setHeader(
          "Set-Cookie",
          AuthenticationController.signTokenAndGetCookie(user._id)
        );
        res.send(user);
        return;
      }
    } catch (error) {
      next(new HttpException(500, "注册失败"));
    }
  };
  /**
   *
   * 登录对象应该只有email和 password两个key
   */
  private login: Handler = async (req, res, next) => {
    const logginData: LogginDto = req.body;
    const loginKeyArr = ["email", "password"];
    const shouldContainOnlyEmailAndPwd = Object.keys(logginData).filter(
      (value) => {
        return !loginKeyArr.includes(value);
      }
    );

    if (shouldContainOnlyEmailAndPwd.length > 0) {
      next(new HttpException(500, "提供了错误的凭证"));
      return;
    }
    // 只有当提供了 正确的 登录信息 （email和 pwd）才继续验证用户是否是注册过的用户
    const user = await this.user.findOne({ email: logginData.email });
    if (user) {
      const checkPassword = await bcrypt.compare(
        logginData.password,
        user.password
      );

      if (checkPassword) {
        user.password = undefined;
        res.setHeader(
          "Set-Cookie",
          AuthenticationController.signTokenAndGetCookie(user._id)
        );
        res.send(user);
        return;
      } else {
        next(new WrongCredentialException());
        return;
      }
    } else {
      next(new WrongCredentialException());
      return;
    }
  };

  private logout: Handler = (req, res, next) => {
    res.setHeader("Set-Cookie", ["Authorization=;Max-age=0"]);
    res.send(200);
  };
}

export default AuthenticationController;
